/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */
package dk.redillusions.jdbc.proxy.statistics;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * The statistics service handles the creation of
 * {@link ConnectionStatisticsDelegate} and the collection of statistics.
 * 
 * @author Sune Wettersteen
 * 
 */
public final class StatisticsService {
    /**
     * The connection service instance.
     */
    private static final StatisticsService INSTANCE = new StatisticsService();

    /**
     * Total connection statistics.
     */
    private final IConnectionStatisticsCollector connectionStatisticsTotal = new ConnectionStatistics();

    /**
     * List of connection statistics to update.
     */
    private final ArrayList<IConnectionStatisticsCollector> connectionStatisticsList = new ArrayList<IConnectionStatisticsCollector>();

    /**
     * Total statement statistics.
     */
    private final IStatementStatisticsCollector statementStatisticsTotal = new StatementStatistics();

    /**
     * Total statement statistics.
     */
    private final IPreparedStatementStatisticsCollector preparedStatementStatisticsTotal = new PreparedStatementStatistics();

    /**
     * List of statement statistics to update.
     */
    private final ArrayList<IStatementStatisticsCollector> statementStatisticsList = new ArrayList<IStatementStatisticsCollector>();

    /**
     * List of prepared statement statistics to update.
     */
    private final ArrayList<IStatementStatisticsCollector> preparedStatementStatisticsList = new ArrayList<IStatementStatisticsCollector>();

    /**
     * The connection statistics invocation handler for connection statistics.
     */
    private ConnectionStatisticsInvocationHandler connectionStatisticsInvocationHandler = new ConnectionStatisticsInvocationHandler();

    /**
     * The statement statistics invocation handler for statement statistics.
     */
    private StatementStatisticsInvocationHandler statementStatisticsInvocationHandler = new StatementStatisticsInvocationHandler();

    /**
     * The statement statistics invocation handler for prepared statement
     * statistics.
     */
    private PreparedStatementStatisticsInvocationHandler preparedStatementStatisticsInvocationHandler = new PreparedStatementStatisticsInvocationHandler();

    /**
     * Constructor.
     */
    private StatisticsService() {
        // Can't be instantiated
    }

    /**
     * Get an instance of the connection service.
     * 
     * @return The instance
     */
    static StatisticsService getInstance() {
        return INSTANCE;
    }

    /**
     * Create a {@link ConnectionStatisticsDelegate} used to hook statistics
     * into {@link java.sql.Connection} instances.
     * 
     * @return The delegate
     */
    private ConnectionStatisticsDelegate newConnectionStatisticsDelegate0() {
        // Create the proxy connection statistics
        final IConnectionStatisticsCollector connectionStatistics = (IConnectionStatisticsCollector) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { IConnectionStatisticsCollector.class },
                this.connectionStatisticsInvocationHandler);

        // Create and return the delegate
        final ConnectionStatisticsDelegate delegate = new ConnectionStatisticsDelegate(connectionStatistics);

        return delegate;
    }

    /**
     * Create a {@link StatementStatisticsDelegate} used to hook statistics into
     * {@link java.sql.Statement} instances.
     * 
     * @return The delegate
     */
    private StatementStatisticsDelegate newStatementStatisticsDelegate0() {
        // Create the proxy statement statistics
        final IStatementStatisticsCollector statementStatistics = (IStatementStatisticsCollector) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { IStatementStatisticsCollector.class },
                this.statementStatisticsInvocationHandler);

        // Create and return the delegate
        final StatementStatisticsDelegate delegate = new StatementStatisticsDelegate(statementStatistics);

        return delegate;
    }

    /**
     * Create a {@link PreparedStatementStatisticsDelegate} used to hook
     * statistics into {@link java.sql.PreparedStatement} instances.
     * 
     * @return The delegate
     */
    private PreparedStatementStatisticsDelegate newPreparedStatementStatisticsDelegate0() {
        // Create the proxy statement statistics
        final IPreparedStatementStatisticsCollector preparedStatementStatistics = (IPreparedStatementStatisticsCollector) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] { IPreparedStatementStatisticsCollector.class },
                this.preparedStatementStatisticsInvocationHandler);

        // Create and return the delegate
        final PreparedStatementStatisticsDelegate delegate = new PreparedStatementStatisticsDelegate(preparedStatementStatistics);

        return delegate;
    }

    /**
     * Create a {@link ConnectionStatisticsDelegate} used to hook statistics
     * into {@link java.sql.Connection} instances.
     * 
     * @return The delegate
     */
    public static ConnectionStatisticsDelegate newConnectionStatisticsDelegate() {
        StatisticsService instance = getInstance();

        return instance.newConnectionStatisticsDelegate0();
    }

    /**
     * Create a {@link StatementStatisticsDelegate} used to hook statistics into
     * {@link java.sql.Statement} instances.
     * 
     * @return The delegate
     */
    public static StatementStatisticsDelegate newStatementStatisticsDelegate() {
        StatisticsService instance = getInstance();

        return instance.newStatementStatisticsDelegate0();
    }

    /**
     * Create a {@link PreparedStatementStatisticsDelegate} used to hook
     * statistics into {@link java.sql.PreparedStatement} instances.
     * 
     * @return The delegate
     */
    public static PreparedStatementStatisticsDelegate newPreparedStatementStatisticsDelegate() {
        StatisticsService instance = getInstance();

        return instance.newPreparedStatementStatisticsDelegate0();
    }

    /**
     * Add a {@link IConnectionStatisticsCollector} to the list of
     * {@link IConnectionStatisticsCollector} which should be updated.
     * 
     * @param connectionStatistics
     *            The {@link IConnectionStatisticsCollector} to add
     */
    void addConnectionStatistics(final IConnectionStatisticsCollector connectionStatistics) {
        this.connectionStatisticsList.add(connectionStatistics);
    }

    /**
     * Remove a {@link IConnectionStatisticsCollector} from the list of
     * {@link IConnectionStatisticsCollector} which should be updated.
     * 
     * @param connectionStatistics
     *            The {@link IConnectionStatisticsCollector} to remove
     */
    void removeConnectionStatistics(final IConnectionStatisticsCollector connectionStatistics) {
        this.connectionStatisticsList.remove(connectionStatistics);
    }

    /**
     * Add a {@link IStatementStatisticsCollector} to the list of
     * {@link IStatementStatisticsCollector} which should be updated.
     * 
     * @param statementStatisticsCollector
     *            The {@link IStatementStatisticsCollector} to add
     */
    void addStatementStatistics(final IStatementStatisticsCollector statementStatisticsCollector) {
        this.statementStatisticsList.add(statementStatisticsCollector);
    }

    /**
     * Remove a {@link IStatementStatisticsCollector} from the list of
     * {@link IStatementStatisticsCollector} which should be updated.
     * 
     * @param statementStatisticsCollector
     *            The {@link IStatementStatisticsCollector} to remove
     */
    void removeStatementStatistics(final IStatementStatisticsCollector statementStatisticsCollector) {
        this.statementStatisticsList.remove(statementStatisticsCollector);
    }

    /**
     * Add a {@link IPreparedStatementStatisticsCollector} to the list of
     * {@link IPreparedStatementStatisticsCollector} which should be updated.
     * 
     * @param preparedStatementStatisticsCollector
     *            The {@link IPreparedStatementStatisticsCollector} to add
     */
    void addPreparedStatementStatistics(final IPreparedStatementStatisticsCollector preparedStatementStatisticsCollector) {
        this.preparedStatementStatisticsList.add(preparedStatementStatisticsCollector);
    }

    /**
     * Remove a {@link IPreparedStatementStatisticsCollector} from the list of
     * {@link IPreparedStatementStatisticsCollector} which should be updated.
     * 
     * @param preparedStatementStatisticsCollector
     *            The {@link IPreparedStatementStatisticsCollector} to remove
     */
    void removePreparedStatementStatistics(final IPreparedStatementStatisticsCollector preparedStatementStatisticsCollector) {
        this.preparedStatementStatisticsList.remove(preparedStatementStatisticsCollector);
    }

    /**
     * {@link java.sql.Connection} statistics invocation handler. Used for
     * handing {@link IConnectionStatisticsCollector} proxies to
     * {@link ConnectionStatisticsDelegate}s.
     * 
     * @author Sune Wettersteen
     * 
     */
    private class ConnectionStatisticsInvocationHandler implements InvocationHandler {
        /**
         * {@inheritDoc}
         */
        @SuppressWarnings("unchecked")
        @Override
        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
            // We don't want any concurrent modifications
            final List<IConnectionStatisticsCollector> statistics = (ArrayList<IConnectionStatisticsCollector>) StatisticsService.this.connectionStatisticsList.clone();

            // Run through all connection statistics and invoke the method
            for (final IConnectionStatisticsCollector connectionStatistics : statistics) {
                method.invoke(connectionStatistics, args);
            }

            // Invoke the method on the total statistics
            return method.invoke(StatisticsService.this.connectionStatisticsTotal, args);
        }

    }

    /**
     * {@link java.sql.Statement} statistics invocation handler. Used for
     * handing {@link AbstractStatementStatistics} proxies to
     * {@link StatementStatisticsDelegate}s.
     * 
     * @author Sune Wettersteen
     * 
     */
    private class StatementStatisticsInvocationHandler implements InvocationHandler {
        /**
         * {@inheritDoc}
         */
        @SuppressWarnings("unchecked")
        @Override
        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
            // We don't want any concurrent modifications
            final List<IStatementStatisticsCollector> statistics = (ArrayList<IStatementStatisticsCollector>) StatisticsService.this.statementStatisticsList.clone();

            // Run through all statement statistics and invoke the method
            for (final IStatementStatisticsCollector statementStatistics : statistics) {
                method.invoke(statementStatistics, args);
            }

            // Invoke the method on the total statistics
            return method.invoke(StatisticsService.this.statementStatisticsTotal, args);
        }

    }

    /**
     * {@link java.sql.PreparedStatement} statistics invocation handler. Used
     * for handing {@link AbstractStatementStatistics} proxies to
     * {@link PreparedStatementStatisticsDelegate}s.
     * 
     * @author Sune Wettersteen
     * 
     */
    private class PreparedStatementStatisticsInvocationHandler implements InvocationHandler {
        /**
         * {@inheritDoc}
         */
        @SuppressWarnings("unchecked")
        @Override
        public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
            // We don't want any concurrent modifications
            final List<IPreparedStatementStatisticsCollector> statistics = (ArrayList<IPreparedStatementStatisticsCollector>) StatisticsService.this.preparedStatementStatisticsList.clone();

            /*
             * Run through all prepared statement statistics and invoke the
             * method
             */
            for (final IPreparedStatementStatisticsCollector statementStatistics : statistics) {
                method.invoke(statementStatistics, args);
            }

            // Invoke the method on the total statistics
            return method.invoke(StatisticsService.this.preparedStatementStatisticsTotal, args);
        }

    }
}
